package com.example.viewdraghelper.view;

import com.nineoldandroids.animation.FloatEvaluator;
import com.nineoldandroids.view.ViewHelper;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.support.v4.widget.ViewDragHelper.Callback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

public class DragLayout extends FrameLayout {
	public static final String TAG = "[DragLayout]";
	public static final float MIN_SCALE_MAIN = 0.8f;
	public static final float MAX_SCALE_MAIN = 1.0f;
	/**拖拽事件的帮助处理类*/
	private ViewDragHelper draghelper;
	/**主界面布局*/
	private View mainLayout;
	/**侧滑菜单布局*/
	private View leftMenu;
	/**左边面板的宽度*/
	private int leftWidth;
	/**Float类型数据的估值器*/
	private FloatEvaluator mFloatEvaluator;
	/**判断手指移动的临界速度*/
	private int criticalVel;
	

	public DragLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	public DragLayout(Context context, AttributeSet attrs) {
		this(context,attrs,0);
	}

	public DragLayout(Context context) {
		this(context,null);
	}
	
	
	private void init(){
		//1.构造:看源码可知,构造的时候,实例化了Scoller 、 VelocityTracker(计速器).
		draghelper = ViewDragHelper.create(this, cb);
		//实例化估值器.
		mFloatEvaluator = new FloatEvaluator();
		//定义手指移动的临界速度,乘以160相当于一秒钟移动1英寸.
		criticalVel = (int) (160*getResources().getDisplayMetrics().density);
	}
	
	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		return super.dispatchTouchEvent(ev);
	}
	
	//2. 将事件交由ViewDragHelper拦截
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		Log.w("零:onInterceptTouchEvent", "ev:"+ev);
		//draghelper.shouldInterceptTouchEvent(ev) 表示draghelper是否应该拦截触摸事件.
		//比如董事长给秘书授权,事情来了,你先拦截一下,看看是不是在你职权范围内的,是的话,你就处理一下,不是的就交给我来处理.
		if(draghelper.shouldInterceptTouchEvent(ev)){
			return true;
		}else{
			return super.onInterceptTouchEvent(ev);
		}
	}
	
	//3. 将事件交由ViewDragHelper处理
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//因为多点触摸可能会有一些bug产生,所以try...cacth一下.
		try {
			draghelper.processTouchEvent(event);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}
	
	/**
	 * ViewDragHelper的内部接口.
	 */
	private Callback cb = new Callback() {
		/**
		 * !!!
		 * 尝试捕获view,提供拖拽的可能性.
		 * 
		 * @param child 手指所在的位置对应的view.
		 * @param pointerId 手指的index,跟多点触摸有关系.
		 * 
		 * @return 是否拦截事件.
		 */
		@Override
		public boolean tryCaptureView(View child, int pointerId) {
			Log.w("一:tryCaptureView", "child:" + child +";"+ " pointerId:"+ pointerId);
			//如果允许所有child都能拖动则:
			return true;  
			
			//如果只允许拖拽菜单布局,可以这么写:
//			return child==leftMenu;
		}
		
		/**
		 * 当view被捕获的时候发生回调,当tryCaptureView返回true时会被调用.
		 * 一般实际开发中,在该方法里边可以触发动画.
		 * @param capturedChild 被捕获的view.
		 * @param activePointerId 手指的index,跟多点触摸有关系.
		 */
		@Override
		public void onViewCaptured(View capturedChild, int activePointerId) {
			Log.w("二:onViewCaptured", "capturedChild:" + capturedChild +";"+ " activePointerId:"+ activePointerId);
		}

		/**
		 * !!!
		 * 作用是限制拖拽范围,或者处理阻力效果(所谓阻力效果即比如手指移动2个像素,但控件只移动1个像素).
		 * @param child 正在被拖拽的view
		 * @param left	预计值/建议值 = child.getLeft() + dx
		 * @param dx	两次触摸事件在水平方向的移动变化量
		 * 
		 * @return ，child 的left 将要移动到的位置
		 */
		@Override
		public int clampViewPositionHorizontal(View child, int left, int dx) {
			Log.d("四(循环):clampViewPositionHorizontal", "child:" + child +";"+ "left:" + left+";"+ "dx:" + dx);
			//实现控制view的拖拽范围.
			if(child==mainLayout){
				left = left<0?0
						:left>leftWidth?leftWidth
								:left;
			}else if(child==leftMenu){
				left = left>0?0
						:left<-leftWidth?-leftWidth
								:left;
			}
			return left;
		}

		@Override
		public int clampViewPositionVertical(View child, int top, int dy) {
			Log.d("五(循环):clampViewPositionVertical", "child:" + child +";"+ "top:" + top+";"+ "dy:" + dy);
			return super.clampViewPositionVertical(child, top, dy);
		}

		/**
		 * !!!
		 * 当changedView的位置发生变化的时候,如果我们想引起其他改变,应重新此方法
		 * @param changedView 位置发生变化的view
		  * @param left changedView 的left位置
		 * @param top   changedView 的top位置
		 * @param dx    与changedView被移动前left的变化
		 * @param dy    与changedView被移动前top的变化
		 */
		@Override
		public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
			Log.d("六(循环):onViewPositionChanged", "changedView:" + changedView +";"+ "left:" + left+";"+ "top:" + top+";"+ "dy:" + dy);
			if(changedView==mainLayout){
				
			}else if(changedView==leftMenu){
				leftMenu.offsetLeftAndRight(-dx); //拖动leftMenu使其保持不动.
				mainLayout.offsetLeftAndRight(dx);//拖动leftMenu使mainLayout同方向变化,相当于将拖动这两个布局的变量都控制在mainLayout上.
			}
			float percent = 1.0f * mainLayout.getLeft()/leftWidth;
			disPatchAnimator(percent);
			
			//调用回调对象的回调方法.
			if(listener!=null){
				listener.onDraging(percent);
			}
		}

		/**
		 * !!!
		 * 当view被释放了,一般会让view归位，而不停在当中
		  * @param releasedChild  被释放的孩子
		 * @param xvel  释放手指时水平方向的速度,单位是像素/秒
		 * @param yvel  释放手指时垂直方向的速度,单位是像素/秒
		 */
		@Override
		public void onViewReleased(View releasedChild, float xvel, float yvel) {
			Log.w("七:onViewReleased", "releasedChild:" + releasedChild +";"+ "xvel:" + xvel+";"+ "yvel:" + yvel);
			//如果手指松开时,滑动的速度高于临界速度,则打开leftMenu;
			//如果手指松开时,滑动的速度小于负临界速度,则关闭leftMenu;
			//如果手指松开时,mainLayout的左边距大于侧滑菜单宽度的一半,则打开leftMenu;
			//如果手指松开时,mainLayout的左边距小于侧滑菜单宽度的一半,则关闭leftMenu;
			if(xvel>criticalVel){//从左往右
				open();
			}else if(xvel<-criticalVel){//从右往左
				close();
			}else if(mainLayout.getLeft()>leftWidth/2){
				open();
			}else{
				close();
			}
		}
		
		/**
		 * !!!
		 * 当view的拖拽状态发生变化了.
		  * @param state 空闲 Idle、正在被拖拽 draging、 自动化settling(即从松手到归位这段时间内的状态)
		 */
		@Override
		public void onViewDragStateChanged(int state) {
			Log.w("三/八:onViewDragStateChanged", "state:" + state);
			if(state==ViewDragHelper.STATE_IDLE){
				if(mainLayout.getLeft()==0){//菜单面板是关闭状态
					updateState(false);
				}else if(mainLayout.getLeft()==leftWidth){//菜单面板是打开状态
					updateState(true);
				}
			}
		}
		
		@Override
		public int getViewHorizontalDragRange(View child) {
			Log.w("getViewHorizontalDragRange", "child:" + child);
			return 1;
		}

		@Override
		public int getViewVerticalDragRange(View child) {
			Log.w("getViewVerticalDragRange", "child:" + child);
			return 1;
		}
	};
	
	/**
	 * 分发动画的方法.
	 * @param percent 当percent=0时,主面板全部显示, 当percent=1时,菜单面板全部显示出来.
	 * 估值器的后面2个参数,表示动画前后的起点和终点,比如缩放动画,起始点是1.0f,终点是0.8f,如果终点为0.0f的话,
	 * 则可以一直缩放到看不见,对于本例,显然缩放到原来的0.8左右就可以了.
	 * 估值器在这里的作用就是根据比例计算当percent=x%时,ScaleX,ScaleY应该是多少?
	 * 计算原理:ScaleX=1.0f+x%*(endX-startX), 相当于这个工作估值器帮我们做了.
	 */
	private void disPatchAnimator(float percent) {
		//主面板动画:1.缩放;
		//菜单面板动画:1.缩放;2.平移;3.透明度;
//		mainLayout.setScaleX(mFloatEvaluator.evaluate(percent, 1.0f, 0.8f)); //第三个参数0.8f是估计的,以实际效果为准.
//		mainLayout.setScaleY(mFloatEvaluator.evaluate(percent, 1.0f, 0.8f));
		
		//使用框架设置属性动画上面来的方便.
		//设置缩放中心点,不要设置为正中间,不然两个面板之前会出现间距,缩放中心点设置在mainLayout的左边界中心点.
		ViewHelper.setPivotX(mainLayout, 0.0f);
		ViewHelper.setPivotY(mainLayout, mainLayout.getHeight()/2f);
		ViewHelper.setScaleX(mainLayout, mFloatEvaluator.evaluate(percent, MAX_SCALE_MAIN, MIN_SCALE_MAIN));
		ViewHelper.setScaleY(mainLayout, mFloatEvaluator.evaluate(percent, MAX_SCALE_MAIN, MIN_SCALE_MAIN));
		
		//菜单面板动画:
		//1.缩放:0.5f--1.0f
		//2.平移:-leftWidth/2--0  只沿X轴平移即可.
		//3.透明度:0.0f--1.0f  完全不透明到透明.
		ViewHelper.setScaleX(leftMenu, mFloatEvaluator.evaluate(percent, 0.5f, 1.0f));
		ViewHelper.setScaleY(leftMenu, mFloatEvaluator.evaluate(percent, 0.5f, 1.0f));
		ViewHelper.setTranslationX(leftMenu, mFloatEvaluator.evaluate(percent, -leftWidth/2f, 0.0f));
		ViewHelper.setAlpha(leftMenu, mFloatEvaluator.evaluate(percent, 0.0f, 1.0f));
	}

	public void close() {
		//使用ViewDragHelper帮助我们判断mainLayout的left和top是否能够平滑的移动到指定的位置.
		//返回ture表示mainLayout没在指定的位置.
		//返回false表示mainLayout已经在指定的位置.
		//内部实际是使用mScroller.startScroll,开启Scroller.
		if(draghelper.smoothSlideViewTo(mainLayout, 0, 0)){
			//在下一帧进行刷新,传的参数是mainLayout的父控件.
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

	public void open() {
		if(draghelper.smoothSlideViewTo(mainLayout, leftWidth, 0)){
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}
	
	protected void updateState(boolean isOpen) {
		if(listener!=null){
			if(isOpen){
				listener.onOpen();
			}else{
				listener.onClose();
			}
		}
	}
	
	@Override
	public void computeScroll() {
		super.computeScroll();
		if(draghelper.continueSettling(true)){
			ViewCompat.postInvalidateOnAnimation(this);
		}
	}

	protected void onFinishInflate() {
		Log.w("onFinishInflate", "onFinishInflate");
		mainLayout = getChildAt(0);
		leftMenu = getChildAt(1);
		//将mainLayout提升到上面一层.
		//这是View的方法,也可以使用ViewGroup的方法:bringChildToFront(mainLayout);
		mainLayout.bringToFront(); 
		mainLayout.requestLayout();
		mainLayout.invalidate();
		
	};
	
	/**
	 * 本方法是介于onMesure()和onLayout()方法之间的方法.
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		//获取一下左边面板的宽度
		if(leftWidth==0){
			leftWidth = leftMenu.getMeasuredWidth();
			Log.w("onSizeChanged", "leftWidth:"+leftWidth);		
		}
	}
	
	//1.定义回调接口.
	//2.提供设置回调对象的方法.
	//3.在合适的时机调用回调对象身上的方法.
	public interface OnDragLayoutStateChangedListener{
		public void onOpen();	//菜单面板打开时
		public void onClose();  //菜单面板关闭时
//		public void onStateChanged(boolean isOpen); //上面两个方法可以合并成一个,灵活把握.
		public void onDraging(float percent);  //拖拽的过程中
	}
	
	/**拖拽状态改变的监听*/
	private OnDragLayoutStateChangedListener listener;
	
	public void setOnDragLayoutStateChangedListener(OnDragLayoutStateChangedListener listener){
		this.listener = listener;
	}
	
}
